
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <glib.h>
#include <glib/gstdio.h>

#include "server.h"

void server_create(Server** server, char* port)
{
    (*server) = (Server*) malloc(sizeof(Server));
    server_initialize((*server),port);
}

int server_initialize(Server* server, char* port)
{
    /* structure pour faire la demande de port*/
    struct addrinfo hints;
    /* structure pour stocker et lire les résultats*/
    struct addrinfo *result, *rp;
    /* variables pour tester si les fonctions donnent un résultats ou une erreur*/
    int res;
    int bon;
    /* Des variable pour contenir de adresse de machine et des numero de port afin de les afficher*/
    char hname[NI_MAXHOST], sname[NI_MAXSERV];

    fileList_initialize(&server->filelist);
    server->port = port;
    pthread_mutex_init(&(server->mutex),NULL);
    pthread_mutex_lock(&(server->mutex));
    dynamicArray_initialize(&server->clients);
    pthread_mutex_unlock(&(server->mutex));
    server->socket = 0;

    /* on rempli la structure hints de demande d'adresse*/
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;    /* IPv4 ou IPv6 */
    hints.ai_socktype = SOCK_STREAM; /* socket flux connecté */
    hints.ai_flags = AI_PASSIVE;    /* Les signifie que toutes les addresse de la machine seront utilisÃ©e */
    hints.ai_protocol = 0;          /* Any protocol */
    hints.ai_addrlen = 0;
    hints.ai_addr = NULL;
    hints.ai_canonname = NULL;
    hints.ai_next = NULL;

    /* on effectue la demande pour le port*/
    res = getaddrinfo(NULL, port, &hints, &result);
    if (res != 0) { /* c'est une erreur*/
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(server->socket));
        return 0;
    }

    /*si res = 0 le véritable résultat de la fontion est l'argument result
     qui contient une liste d'addresse correspondant Ã  la demande on va les
     rester jusqu'a trouver une qui convien*/
    rp = result;
    bon = 0;
    while (rp != NULL) { /* on parcourt la liste pour en trouver une qui convienne*/
        int yes = 1;
        server->socket = socket(rp->ai_family, rp->ai_socktype,rp->ai_protocol);
        /* si le résultat est -1 cela n'a pas fonctionné on recommence avec la prochaine*/
        if (server->socket == -1) {
            perror("Creation de la socket");
            continue;
        }

        res = setsockopt(server->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
        if (res == -1) {
            perror("setsockopt");
            continue;
        }

        /* si la socket a été obtenue, on essaye de réserver le port*/
        res = bind(server->socket, rp->ai_addr, rp->ai_addrlen);
        if (res == 0 ) {/* cela a fonctionné on affiche l'information*/
            bon = 1;

            /* on récupère des informations affichables*/
            res = getnameinfo(rp->ai_addr, rp->ai_addrlen,
                              hname, NI_MAXHOST,
                              sname, NI_MAXSERV,
                              NI_NUMERICSERV|NI_NUMERICHOST);
            if (res != 0) {
                fprintf(stderr, "getnameinfo: %s\n", gai_strerror(res));
                return 0;
            }
            server->address = hname;
            break;
        }
        else { /* sinon le bind a été impossible, il faut fermer la socket*/
            perror("Imposible de reserver l'adresse");
            close (server->socket);
        }

        rp = rp->ai_next;
    }

    if (bon == 0) { /* Cela n'a jamais fonctionné*/
        fprintf(stderr, "Impossible de faire un bind\n");
    }

    listen(server->socket, 5);

    if (res < 0) {
        perror("listen");
        close(server->socket);
        return 0;
    }

    /* on libére la structure devenue inutile*/
    freeaddrinfo(result);
    return 1;
}


int server_accept(Server* server)
{
    struct sockaddr_storage peer_addr;
    socklen_t peer_addr_len = sizeof(peer_addr);
    char hname[NI_MAXHOST], sname[NI_MAXSERV];
    int* client = (int*) malloc(sizeof(int));
    int ret;

    *client = accept (server->socket, (struct sockaddr *)&peer_addr, &peer_addr_len);

    if (*client  == -1) { /* il y a eu une erreur*/
        perror("accept");
        close (server->socket);
        free(client);
        return -1;
    }
    pthread_mutex_lock(&(server->mutex));
    dynamicArray_inserLast(&server->clients,(void*)client);
    pthread_mutex_unlock(&(server->mutex));
    ret = getnameinfo((struct sockaddr*)&peer_addr, peer_addr_len,
                      hname, NI_MAXHOST,
                      sname, NI_MAXSERV,
                      NI_NUMERICSERV);
    if (ret != 0) {
        fprintf(stderr, "getnameinfo: %s\n", gai_strerror(ret));
    }
    fprintf (stdout,"La socket %d a eu un client depuis %s sur le port %s\n",
             server->socket, hname, sname);
    fflush(stdout);
    server_write(*client,"HELO\r\n");
    return (*client);
}

void server_clear(Server* server)
{
    int i;
    int client;
    int *pclient;
    fileList_clear(&server->filelist);
    server->port = 0;
    server->address = "";
    pthread_mutex_lock(&(server->mutex));
    for(i = 0; i < server->clients.count; i++)
    {
        client = server_getClientByIndex(server,0);
        pclient = (int*)server->clients.array[0];
        if(close(client))
            printf("Probleme a la fermeture de la socket de discussion\n");
        dynamicArray_removeFirst(&server->clients);
        free(pclient);
    }
    if(close(server->socket))
        printf("Probleme a la fermeture de la socket de d'attente\n");
    server->socket = 0;
    dynamicArray_clear(&server->clients);
    pthread_mutex_unlock(&(server->mutex));
    pthread_mutex_destroy(&(server->mutex));

}

void server_destroy(Server** server)
{
    server_clear((*server));
    free((*server));
    (*server) = NULL;
}

void server_aquit(int socket, const char* command)
{
    char buff[0xFFFF];
    sprintf(buff,"RCPT %s",command);
    server_write(socket,buff);
}

int server_serveClient(Server* server, int* pclient)
{
    int res = 1;
    char command[0xFFFF];
    int client = *pclient;
    server_readline(client,command,0xFFFF);
    server_aquit(client,command);
    fprintf(stdout,"Client %d : Reception de : %s",client, command);
    fflush(stdout);
    if(server_compareCommand(command,"QUIT\n") || server_compareCommand(command,"QUIT\r\n"))
    {
        server_write(client,"OLEH\r\n");
        fprintf(stdout,"Le client %d a quitter le serveur.\n",client);
        pthread_mutex_lock(&(server->mutex));
        shutdown(client,SHUT_RDWR);
        close(client);
        dynamicArray_remove(&server->clients,(void*)pclient);
        free(pclient);
        pthread_mutex_unlock(&(server->mutex));
        res = 0;
    }
    else if(server_compareCommand(command,"LIST\n") || server_compareCommand(command,"LIST\r\n"))
    {
        server_getList(client,command);
    }
    else if(server_compareCommand(command,"GETF"))
    {
        if(!server_getFile(server,client,command))
            server_write(client,"FAIL\r\n");
    }
    else if(server_compareCommand(command,"FREE"))
    {
        if(!server_freeFile(server,client,command))
            server_write(client,"FAIL\r\n");
    }
    else
    {
        server_write(client,"FAIL\r\n");
    }
    fprintf(stdout,"Client %d : Fin de traitement de : %s",client, command);
    fflush(stdout);
    return res;
}

int server_freeFile(Server* server, int socket, char* command)
{
    char sfile[0xFFFF];
    if(sscanf(command,"FREE %s",sfile) > 0)
    {
        fileList_freeFile(&server->filelist,sfile,socket);
        server_write(socket,"FREE\r\n");
        return 1;
    }
    else
        return 0;
}

int server_getFile(Server* server, int socket, char* command)
{
    char stmp[0xFFFF];
    char sfile[0xFFFF];
    char buff[0xFFFF];
    char* data;
    int lenght;
    FILE* file;
    if(sscanf(command,"GETF %s",stmp) > 0)
    {
        sprintf(sfile,"%s/%s",FILEDIR,stmp);
        switch(fileList_getFile(&server->filelist,sfile,socket))
        {
        case FILESTATE_EXIST:
            file = fopen(sfile,"rb");
            fseek(file,0,SEEK_END);
            lenght = ftell(file);
            fseek(file,0,SEEK_SET);
            sprintf(buff,"SIZE %d\r\n",lenght);
            server_write(socket,buff);
            data = (char*) malloc(sizeof(char) * lenght);
            fread(data,sizeof(char),lenght,file);
            write(socket,(void*)data,sizeof(char) * lenght);
            free(data);
            fclose(file);
            break;
        case FILESTATE_NOTEXIST:
            server_write(socket,"NONE\r\n");
            break;
        case FILESTATE_RESERVED:
            server_write(socket,"BUSY\r\n");
            break;
        }
        return 1;
    }
    else
        return 0;
}

int server_getList(int socket, char* command)
{
    GDir* dir;
    char* path;
    char* fullpath;
    char buff[0xFFFF];
    dir = g_dir_open (FILEDIR,0,NULL);
    if(dir != NULL)
    {
        do
        {
            path = g_dir_read_name (dir);
            if(path != NULL)
            {
                fullpath =(char*) malloc((strlen(FILEDIR)+strlen(path)+2) * sizeof(char));
                sprintf(fullpath, "%s/%s", FILEDIR, (char*)path);
                if(!g_file_test(fullpath,G_FILE_TEST_IS_DIR))
                {
                    if(path != NULL)
                    {
                        sprintf(buff,"FICH %s\r\n",path);
                        server_write(socket,buff);
                    }
                }
                free(fullpath);
            }
        } while(path != NULL);
        server_write(socket,"ENDL\r\n");
        return 1;
    }
    else
    {
        return 0;
    }
}

int server_getClientIndex(const Server* server, int* client)
{
    int i;
    pthread_mutex_lock(&(server->mutex));
    for(i = 0; i < server->clients.count; i++)
    {
        if(((int*)server->clients.array[i]) == client)
        {
            return i;
        }
    }
    pthread_mutex_unlock(&(server->mutex));
    return -1;
}

int * server_getClientAddress(const Server* server, int client)
{
    int i;
    pthread_mutex_lock(&(server->mutex));
    /*Pour chacun des socket client*/
    for(i = 0; i < server->clients.count; i++)
    {
        /*On teste si c'est celui la qui a des données en attente de lecture*/
        if(*((int*)server->clients.array[i]) == client)
        {
            pthread_mutex_unlock(&(server->mutex));
            return (int*)(server->clients.array[i]);
        }
    }
    pthread_mutex_unlock(&(server->mutex));
    return NULL;
}

int server_getClientByIndex(Server *server, int index)
{
    int ret;
    pthread_mutex_lock(&(server->mutex));
    assert((server->clients.count > index) && (index >= 0));
    ret = *((int*)server->clients.array[index]);
    pthread_mutex_unlock(&(server->mutex));
    return ret;
}

int server_compareCommand(const char* line, const char* command)
{
    return (strncmp(line,command,strlen(command)) == 0);
}

int server_compareLine(const char* line, const char* str)
{
    char* _str = (char*) malloc(sizeof(strlen(str) + 3) * sizeof(char));
    strcpy(_str,str);
    strcat(_str,"\r\n");
    if(strcmp(_str,line) == 0)
        return 1;
    else
    {
        strcpy(_str,str);
        strcat(_str,"\n");
        if(strcmp(_str,line) == 0)
            return 1;
        else
            return 0;
    }
    free(_str);
}

void server_write(int fd, char *buffer)
{
    while(*buffer != '\0')
    {
        write(fd,(void*)buffer,sizeof(char));
        buffer++;
    }
}

void server_readline(int fd, char *buffer, int max)
{
    int i;
    if(max <= 0)
    {
        buffer[0] = '\r';
        buffer[1] = '\n';
        buffer[2] = '\0';
    }
    else
    {
        for(i = 0; i < (max-2); i++)
        {
            read(fd,(void*)(&buffer[i]),sizeof(char));
            if(buffer[i] == '\0')
            {
                buffer[i] = '\r';
                buffer[i+1] = '\n';
                buffer[i+2] = '\0';
                break;
            }
            else if(buffer[i] == '\n')
            {
                buffer[i+1] = '\0';
                break;
            }
        }
    }
}
